/*************************************************************************
  > Filename   : awinic_9818.c
  > Author     : oneface - one_face@sina.com
  > Company    : 一尊还酹江月
  > Time       : 2018-05-29 13:34:50

  >Description : 本驱动针对两颗aw9818芯片进行操作，实现18x2颗三色灯的效果
				 具体效果的产品定义详见product目录
				 代码主要逻辑是根据不同效果，向三颗灯寄存器写入不同的亮度值
 - This program is free software; you can redistribute  it and/or modify it
 - under  the terms of  the GNU General  Public License as published by the
 - Free Software Foundation;  either version 2 of the  License, or (at your
 - option) any later version.
 ************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/rtc.h>
#include <asm/gpio.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/kthread.h>

#include "awinic_9818.h"

#define HAVE_CHIPS
//#define A810_BOARD_TEST
//#define AW9818_DEBUG
#ifdef AW9818_DEBUG
#define AW9818_DEBUGP(x,arg...) printk("[AW9818_DEBUG] "x,##arg)
#else
#define AW9818_DEBUGP(...)
#endif

#define DEV_MAJOR   0		// meaning major is not set
#define DEV_MINOR   0		//meaning set minor to be 0
#define DEV_NAME    "xlj-aw9818"	//node "/dev/xlj-aw9818"

#define AW9818_CHANNEL	0	//meaning use i2c-2 adapter, other i2c-0 i2c-1
#define LED_CHIP_NUMS   2	//2 aw9818 chips
#define AW_I2C_RETRIES  2	//i2c rw retry
#define AW_I2C_RETRY_DELAY 5

typedef enum {
	LED_THREAD_ACTIVE = 0,
	LED_THREAD_INACTIVE,
} LED_THREAD_STATUS;

struct aw9818_priv {
	/* dev */
	struct i2c_client *i2c_cli[LED_CHIP_NUMS];	//i2c_cli[0]-->aw9818_1, i2c_cli[1]-->aw9818_2
	struct cdev cdev;
	dev_t devno;
	struct class *class;
	struct device *device;
	/* thread */
	struct mutex ctrl_lock;
	struct mutex effect_lock;
	struct work_struct notify_led_work;
	struct completion led_work_finished;
	LED_THREAD_STATUS led_thread_running;
};
struct aw9818_priv *g_aw9818 = NULL;

//bright value
typedef struct {
	byte r;
	byte g;
	byte b;
} ledreg_data;

//color level
typedef enum {
	LED_COLOR_NONE = 0,
	LED_COLOR_HALF,
	LED_COLOR_FULL,
} LED_COLOR_LEVEL;

typedef struct {
	LED_COLOR_LEVEL r;
	LED_COLOR_LEVEL g;
	LED_COLOR_LEVEL b;
} ledcolor_info;

//interface: effect <---> hardware
typedef struct {
	byte led_nums;
	ledreg_data *p_ledreg_data;
	ledcolor_info *p_ledcolor_info;
} ledintfc_info;
ledintfc_info *g_led_intf = NULL;

//hw--
typedef enum {
	AW9818_1 = 0,
	AW9818_2,
} CHIP_ID;

typedef struct {
	CHIP_ID chip_id;
	byte led_nums;
	const ledreg_data *ledreg_map;
} ledhw_info;

typedef enum {
	ELECTRIC_10mA = (0 << 4),
	ELECTRIC_20mA = (1 << 4),
	ELECTRIC_30mA = (2 << 4),
	ELECTRIC_40mA = (3 << 4),
} ELECTRIC;

#define AW981X_DEFAULT_IMAX    (ELECTRIC_10mA)

static const byte aw981x_chipid_reg = 0x00;
static const byte aw981x_sleep_reg = 0x01;
static const byte aw981x_reset_reg = 0x02;
static const byte aw981x_config_reg = 0x03;
static const byte aw981x_update_reg = 0x04;

static const byte aw981x_chipid_value = 0x18;
static const byte aw981x_en_value = 0x00;
static const byte aw981x_dis_value = 0x80;
static const byte aw981x_reset_value = 0x01;
static const byte aw981x_config_value = (0x02 | AW981X_DEFAULT_IMAX);	// Set default-imax to 10mA, and select led matrix mode
static const byte aw981x_update_value = 0x01;

static const byte gamma_brightness[] = {
	0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 5, 5, 5,
	7, 10, 12, 14, 16, 18, 19, 22, 25,
	27, 27, 30, 33, 35,
	38, 42, 45, 48,
	50, 54, 58, 62, 63,
};

static const byte gamma_levels = sizeof(gamma_brightness) / sizeof(byte);

#define BRIGHT_MAX	7
static const byte led_bright_level[BRIGHT_MAX] = {
	5, 11, 18, 27, 38, 50, 63,
};

typedef struct {
	unsigned int state;
	ledcolor_info background, foward;
	struct led_ctrl_arg ctrl_arg;
} ledeffect_info;
ledeffect_info *g_led_effect = NULL;

static const ledcolor_info led_colors[LED_MAX_COLOR] = {
	{LED_COLOR_NONE, LED_COLOR_NONE, LED_COLOR_NONE},	//none
	{LED_COLOR_FULL, LED_COLOR_NONE, LED_COLOR_NONE},	//red
	{LED_COLOR_FULL, LED_COLOR_HALF, LED_COLOR_NONE},	//orange
	{LED_COLOR_FULL, LED_COLOR_FULL, LED_COLOR_NONE},	//yellow
	{LED_COLOR_NONE, LED_COLOR_FULL, LED_COLOR_NONE},	//green
	{LED_COLOR_NONE, LED_COLOR_NONE, LED_COLOR_FULL},	//blue
	{LED_COLOR_NONE, LED_COLOR_FULL, LED_COLOR_FULL},	//indigo
	{LED_COLOR_FULL, LED_COLOR_NONE, LED_COLOR_FULL},	//purple
	{LED_COLOR_FULL, LED_COLOR_FULL, LED_COLOR_FULL},	//white
};

static const ledreg_data ledreg_map_9818_1[] = {
	{0x16, 0x17, 0x18},	//C1 R7,8,9  //RGB01
	{0x21, 0x22, 0x23},	//C2 R7,8,9
	{0x2c, 0x2d, 0x2e},	//C3 R7,8,9
	{0x37, 0x38, 0x39},	//C4 R7,8,9
	{0x42, 0x43, 0x44},	//C5 R7,8,9
	{0x4d, 0x4e, 0x4f},	//C6 R7,8,9
	{0x13, 0x14, 0x15},	//C1 R4,5,6
	{0x1e, 0x1f, 0x20},	//C2 R4,5,6
	{0x29, 0x2a, 0x2b},	//C3 R4,5,6
	{0x34, 0x35, 0x36},	//C4 R4,5,6
	{0x3f, 0x40, 0x41},	//C5 R4,5,6
	{0x4a, 0x4b, 0x4c},	//C6 R4,5,6
	{0x10, 0x11, 0x12},	//C1 R1,2,3
	{0x1b, 0x1c, 0x1d},	//C2 R1,2,3
	{0x26, 0x27, 0x28},	//C3 R1,2,3
	{0x31, 0x32, 0x33},	//C4 R1,2,3
	{0x3c, 0x3d, 0x3e},	//C5 R1,2,3
	{0x47, 0x48, 0x49},	//C6 R1,2,3  //RGB18
};

static const ledreg_data ledreg_map_9818_2[] = {
	{0x47, 0x48, 0x49},	//C6 R1,2,3  //RGB19
	{0x10, 0x11, 0x12},	//C1 R1,2,3
	{0x1b, 0x1c, 0x1d},	//C2 R1,2,3
	{0x26, 0x27, 0x28},	//C3 R1,2,3
	{0x31, 0x32, 0x33},	//C4 R1,2,3
	{0x3c, 0x3d, 0x3e},	//C5 R1,2,3
	{0x4a, 0x4b, 0x4c},	//C6 R4,5,6
	{0x13, 0x14, 0x15},	//C1 R4,5,6
	{0x1e, 0x1f, 0x20},	//C2 R4,5,6
	{0x29, 0x2a, 0x2b},	//C3 R4,5,6
	{0x34, 0x35, 0x36},	//C4 R4,5,6
	{0x3f, 0x40, 0x41},	//C5 R4,5,6
	{0x4d, 0x4e, 0x4f},	//C6 R7,8,9
	{0x16, 0x17, 0x18},	//C1 R7,8,9
	{0x21, 0x22, 0x23},	//C2 R7,8,9
	{0x2c, 0x2d, 0x2e},	//C3 R7,8,9
	{0x37, 0x38, 0x39},	//C4 R7,8,9
	{0x42, 0x43, 0x44},	//C5 R7,8,9  //RGB36
};

static const ledhw_info ledhw_info_struct[LED_CHIP_NUMS] = {
	{
	 AW9818_1,
	 18,
	 ledreg_map_9818_1,
	 },
	{
	 AW9818_2,
	 18,
	 ledreg_map_9818_2,
	 },
};

/**************************************************************************************************************/

static inline const ledhw_info *get_ledhw_info(byte i)
{
	return &ledhw_info_struct[i];
}

static inline byte get_led_nums(byte i)
{
	const ledhw_info *p_ledhw_info = get_ledhw_info(i);
	return p_ledhw_info->led_nums;
}

static inline void set_ledintfc_info(ledintfc_info * ledinterface)
{
	g_led_intf = ledinterface;
}

static int ledintfc_info_init(void)
{
	byte i, len;
	ledintfc_info *ledinterface = (ledintfc_info *) kmalloc(sizeof(ledintfc_info) * LED_CHIP_NUMS, GFP_KERNEL);

	AW9818_DEBUGP("ledintfc_info_init\n");

	if (NULL != ledinterface) {
		for (i = 0; i < LED_CHIP_NUMS; i++) {
			ledinterface[i].led_nums = get_led_nums(i);
			len = sizeof(ledreg_data) * (ledinterface[i].led_nums);
			ledinterface[i].p_ledreg_data = (ledreg_data *) kmalloc(len, GFP_KERNEL);
			if (NULL != ledinterface[i].p_ledreg_data) {
				memset((void *)ledinterface[i].p_ledreg_data, 0, len);
			} else {
				AW9818_DEBUGP("ledinterface[%d].p_ledreg_data is NULL\n", i);
				return -ENOMEM;
			}
			len = sizeof(ledcolor_info) * (ledinterface[i].led_nums);
			ledinterface[i].p_ledcolor_info = (ledcolor_info *) kmalloc(len, GFP_KERNEL);
			if (NULL != ledinterface[i].p_ledcolor_info) {
				memset((void *)ledinterface[i].p_ledcolor_info, 0, len);
			} else {
				AW9818_DEBUGP("ledinterface[%d].p_ledcolor_info is NULL\n", i);
				return -ENOMEM;
			}
		}
	} else {
		AW9818_DEBUGP("ledinterface kmalloc failed\n");
		return -ENOMEM;
	}

	set_ledintfc_info(ledinterface);

	return 0;
}

static inline void set_ledeffect_info(ledeffect_info * ledeffection)
{
	g_led_effect = ledeffection;
}

static inline ledeffect_info *get_led_effect(void)
{
	return g_led_effect;
}

static int ledeffect_info_init(void)
{
	ledeffect_info *ledeffection = NULL;
	struct led_ctrl_arg ctrl_arg_initializition = {
		.orientation = 0,
		.color = (LED_COLOR) none,
		.brightness = 0,
		.airkiss_config = false,
		.is_mute = false,
		.apk_ota_progress = 0,
		.volume = 0,
	};

	AW9818_DEBUGP("ledeffect_info_init\n");

	ledeffection = (ledeffect_info *) kmalloc(sizeof(ledeffect_info), GFP_KERNEL);
	if (ledeffection) {
		memset(&ledeffection->ctrl_arg, 0, sizeof(ledeffection->ctrl_arg));
		memcpy(&ledeffection->ctrl_arg, &ctrl_arg_initializition, sizeof(struct led_ctrl_arg));

		ledeffection->background = led_colors[none];
		ledeffection->foward = led_colors[none];
		set_ledeffect_info(ledeffection);
	} else {
		AW9818_DEBUGP("ledeffection kmalloc failed\n");
		return -ENOMEM;
	}

	return 0;
}

static int aw9818_i2c_write(byte reg_addr, byte reg_data, struct i2c_client *client)
{
	int ret = -1;
	byte cnt = 0;

	while (cnt < AW_I2C_RETRIES) {
		ret = i2c_smbus_write_byte_data(client, reg_addr, reg_data);
		if (ret < 0) {
			AW9818_DEBUGP("%s: i2c_write cnt=%d error=%d\n", __func__, cnt, ret);
		} else {
			break;
		}
		cnt++;
		msleep(AW_I2C_RETRY_DELAY);
	}

	return ret;
}

static void aw981x_write_register(CHIP_ID chip_id, byte reg, byte data)
{
	AW9818_DEBUGP("aw981x_write_register\n");
	AW9818_DEBUGP("id 0x%x, reg 0x%x, data 0x%x\n", chip_id, reg, data);

#ifdef HAVE_CHIPS
	aw9818_i2c_write(reg, data, g_aw9818->i2c_cli[chip_id]);
#endif
}

static int aw9818_i2c_read(byte reg_addr, byte * reg_data, struct i2c_client *client)
{
	int ret = -1;
	byte cnt = 0;

	while (cnt < AW_I2C_RETRIES) {
		ret = i2c_smbus_read_byte_data(client, reg_addr);
		if (ret < 0) {
			AW9818_DEBUGP("%s: i2c_read cnt=%d error=%d\n", __func__, cnt, ret);
		} else {
			*reg_data = ret;
			break;
		}
		cnt++;
		msleep(AW_I2C_RETRY_DELAY);
	}

	return ret;
}

static inline void aw981x_read_register(CHIP_ID chip_id, byte reg, byte * data)
{
	AW9818_DEBUGP("aw981x_read_register\n");
	AW9818_DEBUGP("id 0x%x, reg 0x%x\n", chip_id, reg);

#ifdef HAVE_CHIPS
	aw9818_i2c_read(reg, data, g_aw9818->i2c_cli[chip_id]);
#endif
}

/*
 *
 * a composer function
 */
static byte convert_brightness_to_data(byte brightness, LED_COLOR_LEVEL color)
{
	AW9818_DEBUGP("convert_brightness_to_data\n");

	switch (color) {
	case LED_COLOR_NONE:
		brightness &= 0;
		break;
	case LED_COLOR_HALF:
		brightness <<= 1;
		break;
		/* LED_COLOR_FULL and default, brightness is not changed */
	default:
		break;
	}

	return brightness;
}

static void led_update_regdata(byte led_index, ledreg_data * p_ledreg_data)
{
	const ledhw_info *p_ledhw_info = NULL;
	const ledreg_data *lreg = NULL;
	AW9818_DEBUGP("led_update_regdata\n");

	/* get regmap(r,g,b) and data(r,g,b) */
	if (led_index < get_led_nums(0)) {
		p_ledhw_info = get_ledhw_info(0);
	} else {
		p_ledhw_info = get_ledhw_info(1);
		led_index -= get_led_nums(0);
	}
	lreg = p_ledhw_info->ledreg_map + led_index;

	aw981x_write_register(p_ledhw_info->chip_id, lreg->r, p_ledreg_data->r);
	aw981x_write_register(p_ledhw_info->chip_id, lreg->g, p_ledreg_data->g);
	aw981x_write_register(p_ledhw_info->chip_id, lreg->b, p_ledreg_data->b);
}

static void led_set_bright_color(byte led_index, byte brightness, const ledcolor_info color)
{
	ledintfc_info *ledinterface = NULL;

	AW9818_DEBUGP("led_set_bright_color\n");

	//led_index %= get_led_nums(0) + get_led_nums(1);
	led_index %= 36;
	if (led_index < 18) {
		ledinterface = &g_led_intf[0];
	} else {
		ledinterface = &g_led_intf[1];
	}

	if (NULL != ledinterface && NULL != ledinterface->p_ledreg_data) {
		ledinterface->p_ledreg_data->r = convert_brightness_to_data(brightness, color.r);
		ledinterface->p_ledreg_data->g = convert_brightness_to_data(brightness, color.g);
		ledinterface->p_ledreg_data->b = convert_brightness_to_data(brightness, color.b);

		led_update_regdata(led_index, ledinterface->p_ledreg_data);
	} else if (ledinterface == NULL) {
		AW9818_DEBUGP("some struct is NULL\n");
	}

}

static void led_set_all_bright_color(byte brightness, const ledcolor_info background)
{
	byte i = 0;
	byte led_nums = get_led_nums(0) + get_led_nums(1);

	for (; i < led_nums; i++) {
		AW9818_DEBUGP("led %u, led_set_bright_color\n", i);
		led_set_bright_color(i, brightness, background);
	}
}

static void aw981x_enable_chip(void)
{
	byte aw981x_id = 0;
	AW9818_DEBUGP("aw981x_enable_chip\n");

	for (; aw981x_id < LED_CHIP_NUMS; aw981x_id++) {
		/* enable chip */
		aw981x_write_register(aw981x_id, aw981x_sleep_reg, aw981x_en_value);
	}
}

static void aw981x_update_chip(void)
{
	byte aw981x_id = 0;
	AW9818_DEBUGP("aw981x_update_chip\n");

	for (; aw981x_id < LED_CHIP_NUMS; aw981x_id++) {
		aw981x_write_register(aw981x_id, aw981x_update_reg, aw981x_update_value);
	}
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
	udelay(1000);
}

static void breath_aw981x_update_chip(void)
{
	byte aw981x_id = 0;
	AW9818_DEBUGP("aw981x_update_chip\n");

	for (; aw981x_id < LED_CHIP_NUMS; aw981x_id++) {
		aw981x_write_register(aw981x_id, aw981x_update_reg, aw981x_update_value);
	}
	udelay(1000);
	udelay(1000);
}

static bool led_check_all_chipid(void)
{
	byte aw981x_id = 0;
	bool re = false;
	byte chip_id = 0;
	AW9818_DEBUGP("led_check_chipid\n");

	for (; aw981x_id < LED_CHIP_NUMS; aw981x_id++) {
		aw981x_read_register(aw981x_id, aw981x_chipid_reg, &chip_id);
		if (aw981x_chipid_value == chip_id) {
			re = true;
		} else {
			AW9818_DEBUGP("check aw981x_id = %d is failed\n", aw981x_id);
			return false;
		}
	}

	return re;
}

static void led_default_setup(void)
{
	byte aw981x_id = 0;

	AW9818_DEBUGP("led_default_setup\n");

	for (; aw981x_id < LED_CHIP_NUMS; aw981x_id++) {
		/* reset chip */
		aw981x_write_register(aw981x_id, aw981x_reset_reg, aw981x_reset_value);
		/* configure Imax and mode */
		aw981x_write_register(aw981x_id, aw981x_config_reg, aw981x_config_value);
	}

	aw981x_enable_chip();
}

/*
 *
 *所有的灯关闭，但保持芯片处于工作状态
 */
static void led_effect_close(void)
{
	byte brightness;
	ledcolor_info background;

	AW9818_DEBUGP("led_effect_close\n");

	background = led_colors[none];
	brightness = 0;

	led_set_all_bright_color(brightness, background);
	aw981x_update_chip();
}

static void aw9818_comet_init_status(byte comet_nums, byte back_brightness, const ledcolor_info background,
				     const ledcolor_info foward)
{
	register i;

	for (i = 0; i < comet_nums; i++) {
		led_set_bright_color(i, led_bright_level[i], foward);
	}
	for (; i < 36; i++) {
		led_set_bright_color(i, back_brightness, background);
	}

	aw981x_update_chip();
}

static void effect_comet_set(byte * cur_idx, byte comet_nums, const ledcolor_info background, const ledcolor_info foward)
{
	register i;

	for (i = 1; i < comet_nums; i++) {
		led_set_bright_color(*cur_idx + 1 + i, led_bright_level[i], foward);
	}
	aw981x_update_chip();
	(*cur_idx)++;
	*cur_idx %= 36;
}

/**************************************************************************************************************/

/*
 *
 * 一圈浅蓝色的灯亮起，浅红色的灯以 彗星 模式转动
 */
static void led_effect_startup(void)
{
	ledcolor_info background, foward;
	byte cur_idx = 0;
	byte comet_nums, back_brightness;

	AW9818_DEBUGP("led_effect_startup\n");

	comet_nums = sizeof(led_bright_level) / sizeof(led_bright_level[0]);
	back_brightness = led_bright_level[comet_nums - 2];	/* 故意设置背景色比comet最大值暗一些,好看 */
	background = led_colors[indigo];
	foward = led_colors[purple];

	aw9818_comet_init_status(comet_nums, back_brightness, background, foward);
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE) {
		led_set_bright_color(cur_idx, back_brightness, background);
		effect_comet_set(&cur_idx, comet_nums, background, foward);
	}
}

/*
 *
 * 一圈白色的灯亮起，
 * 浅蓝色的灯以 彗星 模式转动
 */
static void led_effect_complete(void)
{
	ledcolor_info background, foward;
	unsigned long timeout = jiffies + HZ * 5;
	byte cur_idx = 0;
	byte comet_nums, back_brightness;

	AW9818_DEBUGP("led_effect_complete\n");

	comet_nums = sizeof(led_bright_level) / sizeof(led_bright_level[0]);
	back_brightness = led_bright_level[comet_nums - 2];	/* 故意设置背景色比comet最大值暗一些,好看 */
	background = led_colors[white];
	foward = led_colors[indigo];

	aw9818_comet_init_status(comet_nums, back_brightness, background, foward);
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		led_set_bright_color(cur_idx, back_brightness, background);
		effect_comet_set(&cur_idx, comet_nums, background, foward);
	}
}

/*
 *
 * 一圈橙色的灯亮起,默认1分钟后退出
 */
static void led_effect_airkiss_mode(void)
{
	byte brightness;
	ledcolor_info background;
	unsigned long timeout = jiffies + HZ * 60;

	AW9818_DEBUGP("led_effect_airkiss_mode\n");

	brightness = led_bright_level[BRIGHT_MAX - 1];
	background = led_colors[orange];

	led_set_all_bright_color(brightness, background);
	aw981x_update_chip();

	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		msleep(100);
	}
}

/*
 *
 * 浅黄色的灯以 彗星 模式转动,默认两分钟退出
 */
static void led_effect_airkiss_config(void)
{
	ledcolor_info background, foward;
	byte cur_idx = 0;
	byte comet_nums, back_brightness;
	unsigned long timeout = jiffies + HZ * 120;

	AW9818_DEBUGP("led_effect_airkiss_config\n");

	comet_nums = sizeof(led_bright_level) / sizeof(led_bright_level[0]);
	back_brightness = led_bright_level[comet_nums - 2];	/* 故意设置背景色比comet最大值暗一些,好看 */
	background = led_colors[none];
	foward = led_colors[yellow];

	aw9818_comet_init_status(comet_nums, back_brightness, background, foward);
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		led_set_bright_color(cur_idx, back_brightness, background);
		effect_comet_set(&cur_idx, comet_nums, background, foward);
	}
}

/*
 *
 * 联网成功后，所有灯都熄灭
 */
static void led_effect_airkiss_connect(void)
{
	AW9818_DEBUGP("led_effect_airkiss_connect\n");
	led_effect_close();
}

/*
 *
 * 一圈蓝色灯亮起，发声方向处(0 ~ 360)变为白色
 */
static void led_effect_wake_up(int orientation)
{
	int v_index;
	ledcolor_info background, foward;
	byte back_brightness, foward_brightness;
	unsigned long timeout = jiffies + 3 * HZ;
	byte cur_idx;

	AW9818_DEBUGP("led_effect_wake_up\n");

	if (orientation < 0 || orientation > 360) {
		AW9818_DEBUGP("voice orientation invalid\n");
		return;
	}

	back_brightness = led_bright_level[BRIGHT_MAX - 3];
	foward_brightness = led_bright_level[BRIGHT_MAX - 1];
	background = led_colors[blue];
	foward = led_colors[white];
	led_set_all_bright_color(back_brightness, background);

	v_index = (orientation / 10);
	if (v_index == 0 || v_index == 36) {
		led_set_bright_color(35, foward_brightness, foward);
		led_set_bright_color(0, foward_brightness, foward);
		led_set_bright_color(1, foward_brightness, foward);
	} else if (v_index == 35) {
		led_set_bright_color(34, foward_brightness, foward);
		led_set_bright_color(35, foward_brightness, foward);
		led_set_bright_color(0, foward_brightness, foward);
	} else {
		for (cur_idx = v_index - 1; cur_idx <= v_index + 1; cur_idx++) {
			led_set_bright_color(cur_idx, foward_brightness, foward);
		}
	}
	aw981x_update_chip();

	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		msleep(100);
	}
}

/*
 *
 * 一圈蓝色灯闪烁一下后，全部熄灭
 */
static void led_effect_command_fail(void)
{
	ledcolor_info color;
	byte brightness;
	unsigned long timeout = jiffies + 2 * HZ;

	AW9818_DEBUGP("led_effect_command_fail\n");

	brightness = led_bright_level[BRIGHT_MAX - 1];
	color = led_colors[blue];
	led_set_all_bright_color(brightness, color);
	aw981x_update_chip();
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		msleep(100);
	}
	led_effect_close();
}

/*
 *
 * 一圈灯由蓝色和浅蓝色交替变换 5s
 */
static void led_effect_command_success(void)
{
	ledcolor_info color;
	byte bright_idx, color_idx, brightness, max_idx = 2 * gamma_levels, gamma_idx = 0;
	unsigned long timeout = jiffies + HZ * 10;
#ifdef TWO_COLOR
	ledcolor_info breath_colors[] = {
		led_colors[blue],
		led_colors[indigo],
	};
#endif

	AW9818_DEBUGP("led_effect_command_success\n");

	bright_idx = gamma_idx % max_idx;
#ifdef TWO_COLOR
	color_idx = gamma_idx / max_idx;
#endif
	color = led_colors[blue];
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
#ifdef TWO_COLOR
		color = breath_colors[color_idx];
#endif

		if (bright_idx < gamma_levels) {
			brightness = gamma_brightness[bright_idx];
		} else {
			brightness = gamma_brightness[max_idx - bright_idx - 1];
		}

		led_set_all_bright_color(brightness, color);
		breath_aw981x_update_chip();
		gamma_idx++;
		bright_idx = gamma_idx % max_idx;
#ifdef TWO_COLOR
		color_idx = gamma_idx / gamma_levels;
#endif
		gamma_idx %= max_idx;
	}
}

/*
 *
 * 一圈灯变成红色(mute)
 */
static void led_effect_keymute(void)
{
	byte brightness;
	ledcolor_info background;

	AW9818_DEBUGP("led_effect_keymute\n");

	brightness = led_bright_level[BRIGHT_MAX - 1];
	background = led_colors[red];

	led_set_all_bright_color(brightness, background);
	aw981x_update_chip();

	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE) {
		msleep(100);
	}

}

/*
 *
 * 一圈灯的颜色消失(unmute)
 */
static void led_effect_keyunmute(void)
{
	led_effect_close();
}

/*
 *
 *volume up:从RGB01 RGB036向闭环方向增长; volume down:反向
 *(2:10) (3:4) (4:1), 即每2颗为增长粒度有10个index类推
 */
static void led_effect_volumechange(byte volume)
{
	byte v, i, j, n;
	ledcolor_info color, background;
	byte brightness;
	unsigned long timeout = jiffies + 2 * HZ;

	brightness = led_bright_level[BRIGHT_MAX - 1];
	color = led_colors[green];
	background = led_colors[none];
	if (volume < 0) {
		volume = 0;
	} else if (volume > 15) {	//数据来自Android上层index分布
		volume = 15;
	}

	led_set_all_bright_color(brightness, background);
	/*if volume=0 :2颗红， volume=1 :2颗绿 */
	i = j = 0;
	v = 1;
	while (g_aw9818->led_thread_running == LED_THREAD_ACTIVE && time_before(jiffies, timeout)) {
		if (0 == volume) {
			color = led_colors[red];
			n = 2;
			for (j = 0; j < n; j++) {
				led_set_bright_color(i + j, brightness, color);
			}
		} else {
			while (v <= volume) {
				if (v < 11) {
					n = 2;
				} else if (v < 15) {
					n = 3;
				} else {
					n = 4;
				}
				for (j = 0; j < n; j++) {
					led_set_bright_color(i + j, brightness, color);
				}
				i = i + j;
				v++;
			}
		}

		aw981x_update_chip();
	}
}

#ifdef A810_BOARD_TEST
static void test_aw9818_color()
{
	ledcolor_info color;
	byte bright_level;

	int i;
	while (1) {
		for (i = 0; i < LED_MAX_COLOR; i++) {
			bright_level = led_bright_level[BRIGHT_MAX - 1];
			color = led_colors[i];
			led_set_all_bright_color(bright_level, color);
			aw981x_update_chip();
			msleep(500);
		}
	}
}

static void test_white_color()
{
	ledcolor_info color;
	byte bright_level;

	bright_level = led_bright_level[BRIGHT_MAX - 1];
	color = led_colors[white];
	led_set_all_bright_color(bright_level, color);
	aw981x_update_chip();
}

static void test_aw9818_comet()
{
	ledcolor_info background, foward;
	byte cur_idx = 0;
	byte i, comet_nums, back_brightness;

	background = led_colors[white];
	foward = led_colors[purple];
	comet_nums = sizeof(led_bright_level) / sizeof(led_bright_level[0]);
	back_brightness = led_bright_level[comet_nums - 2];	/* 故意设置背景色比comet最大值暗一些,好看 */

	aw9818_comet_init_status(comet_nums, back_brightness, background, foward);

	/* set comet, 仅仅更新变化的节点的状态 */
	while (1) {		// 修改成条件
		led_set_bright_color(cur_idx, back_brightness, background);
		for (i = 1; i < comet_nums; i++) {
			led_set_bright_color(cur_idx + 1 + i, led_bright_level[i], foward);
		}
		aw981x_update_chip();
		cur_idx++;
		cur_idx %= 36;
	}
}

static void test_aw9818_blink()
{
	ledcolor_info background, foward;
	byte back_brightness;

	background = led_colors[indigo];
	foward = led_colors[purple];

	back_brightness = led_bright_level[4];
	led_set_all_bright_color(back_brightness, background);
	aw981x_update_chip();
	while (1) {
		led_set_bright_color(0, back_brightness, foward);
		aw981x_update_chip();
		msleep(LED_EFFECT_STARTUP_DELAY);
		led_set_bright_color(0, back_brightness, background);
		aw981x_update_chip();
		msleep(LED_EFFECT_STARTUP_DELAY);
	}
}

static void aw9818_test()
{
	/*test color */
	test_aw9818_color();

	/*test comet show */
	//test_aw9818_comet();

	/*test blink, 闪烁时间短引起晃眼睛，是因为频繁通信电流不稳定吗，本测试验证该问题 */
	//test_aw9818_blink();
}
#endif

static void led_event_cntrl_work(struct work_struct *work)
{
	ledeffect_info *p_led_effect = NULL;

	mutex_lock(&g_aw9818->effect_lock);
	p_led_effect = get_led_effect();
	if (NULL != p_led_effect) {
		g_aw9818->led_thread_running = LED_THREAD_ACTIVE;

		switch (p_led_effect->state) {
		case AW9818_LEDS_EFFECT_STARTUP:
			led_effect_startup();
			break;
		case AW9818_LEDS_EFFECT_COMPLETE:
			led_effect_complete();
			break;
		case AW9818_LEDS_EFFECT_AIRKISS_MODE:
			led_effect_airkiss_mode();
			break;
		case AW9818_LEDS_EFFECT_AIRKISS_CONFIG:
			led_effect_airkiss_config();
			break;
		case AW9818_LEDS_EFFECT_AIRKISS_CONNECTED:
			led_effect_airkiss_connect();
			break;
		case AW9818_LEDS_EFFECT_WAKE_UP:
			led_effect_wake_up(p_led_effect->ctrl_arg.orientation);
			break;
		case AW9818_LEDS_EFFECT_COMMAND_FAIL:
			led_effect_command_fail();
			break;
		case AW9818_LEDS_EFFECT_COMMAND_SUCCESS:
			led_effect_command_success();
			break;
		case AW9818_LEDS_EFFECT_KEYMUTE:
			led_effect_keymute();
			break;
		case AW9818_LEDS_EFFECT_KEYUNMUTE:
			led_effect_keyunmute();
			break;
		case AW9818_LEDS_EFFECT_VOLUME_CHANGE:
			led_effect_volumechange(p_led_effect->ctrl_arg.volume);
			break;
		default:
			break;
		}

		//FIXME 一些循环的可以简化成不改变状态,比如静音的红色设置好以后何必重复的while不退出呢 还有volume的?
		/* close all leds */
		led_effect_close();

		/* if airkiss config is interrupted, but not complete, recovery airkiss config effect */
		if (p_led_effect->state != AW9818_LEDS_EFFECT_AIRKISS_CONFIG && p_led_effect->ctrl_arg.airkiss_config) {
			led_effect_airkiss_config();
		}

		/* if key in keymute; recovery keymute effect */
		if (p_led_effect->state != AW9818_LEDS_EFFECT_KEYMUTE) {
			p_led_effect->ctrl_arg.is_mute ? led_effect_keymute() : led_effect_close();
		}

		g_aw9818->led_thread_running = LED_THREAD_INACTIVE;
		complete(&g_aw9818->led_work_finished);
	}
	mutex_unlock(&g_aw9818->effect_lock);
}

/*
 *
 *一些东西的初始化等...
 */
static int aw9818_led_init(void)
{
	AW9818_DEBUGP("aw9818_led_init...\n");

	if (0 != ledintfc_info_init()) {
		AW9818_DEBUGP("ledintfc_info_init failed\n");
		return -1;
	}

	if (!led_check_all_chipid()) {
		AW9818_DEBUGP("Maybe this is not aw9818 chip\n");
		return -1;
	}

	led_default_setup();

	if (0 != ledeffect_info_init()) {
		AW9818_DEBUGP("ledeffect_info_init failed\n");
		return -1;
	}
	/* for interrupt event */
	mutex_init(&g_aw9818->ctrl_lock);

	/* for work */
	mutex_init(&g_aw9818->effect_lock);

	/* for interrrupt&work sync */
	init_completion(&g_aw9818->led_work_finished);

	g_aw9818->led_thread_running = LED_THREAD_INACTIVE;

	INIT_WORK(&g_aw9818->notify_led_work, led_event_cntrl_work);

	return 0;
}

static void aw9818_led_uninit(void)
{
	AW9818_DEBUGP("aw9818_led_uninit...\n");
	//TODO

}

static int aw9818_open(struct inode *inode, struct file *filp)
{
	struct aw9818_priv *aw9818 = container_of(inode->i_cdev,
						  struct aw9818_priv, cdev);

	filp->private_data = aw9818;	/* FIXME unuse, we use global g_aw9818 */

	return 0;
}

static void do_ctrl_event(unsigned int cmd, struct led_ctrl_arg *p_ctrl_arg)
{
	ledeffect_info *p_led_effect = get_led_effect();

	if (NULL != p_led_effect) {
		/* filter blacklist */
		if (p_led_effect->ctrl_arg.is_mute
		    && (cmd == AW9818_LEDS_EFFECT_WAKE_UP || cmd == AW9818_LEDS_EFFECT_COMMAND_FAIL
			|| cmd == AW9818_LEDS_EFFECT_COMMAND_SUCCESS)) {
			return;
		}

		if (g_aw9818->led_thread_running == LED_THREAD_ACTIVE) {
			/* stop led_event_cntrl_work */
			g_aw9818->led_thread_running = LED_THREAD_INACTIVE;

			/* sync led_event_cntrl_work finished */
			wait_for_completion(&g_aw9818->led_work_finished);
		} else {
		}

		mutex_lock(&g_aw9818->ctrl_lock);

		p_led_effect->state = cmd;
		if (NULL != p_ctrl_arg) {
			switch (cmd) {
			case AW9818_LEDS_EFFECT_WAKE_UP:
				p_led_effect->ctrl_arg.orientation = p_ctrl_arg->orientation;
				break;
			case AW9818_LEDS_EFFECT_KEYMUTE:
				/* now jump into case next */
			case AW9818_LEDS_EFFECT_KEYUNMUTE:
				p_led_effect->ctrl_arg.is_mute = p_ctrl_arg->is_mute;
				break;
			case AW9818_LEDS_EFFECT_AIRKISS_CONFIG:
				p_led_effect->ctrl_arg.airkiss_config = true;
				break;
			case AW9818_LEDS_EFFECT_AIRKISS_CONNECTED:
				p_led_effect->ctrl_arg.airkiss_config = false;
				break;
			case AW9818_LEDS_EFFECT_VOLUME_CHANGE:
				p_led_effect->ctrl_arg.volume = p_ctrl_arg->volume;
				break;
			default:
				break;
			}
		}

		/* schedule led_event_cntrl_work */
		schedule_work(&g_aw9818->notify_led_work);

		mutex_unlock(&g_aw9818->ctrl_lock);
	}
}

/*
 * 注意解析bool型,不可以bool is_mute
 */
static long aw9818_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct led_ctrl_arg ctrl_arg;

	memset(&ctrl_arg, 0, sizeof(ctrl_arg));
	if ((void *)arg != NULL && copy_from_user(&ctrl_arg, (void __user *)arg, sizeof(struct led_ctrl_arg))) {
		return -EFAULT;
	}

	do_ctrl_event(cmd, &ctrl_arg);

	return 0;
}

static int aw9818_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static struct file_operations aw9818_fops = {
	.owner = THIS_MODULE,
	.open = aw9818_open,
	.unlocked_ioctl = aw9818_unlocked_ioctl,
	.release = aw9818_release,
};

static int aw9818_setup_cdev(struct aw9818_priv *data)
{
	int ret = -1;
	const int dev_major = DEV_MAJOR;
	const int dev_minor = DEV_MINOR;

	data->devno = MKDEV(dev_major, dev_minor);
	if (dev_major) {
		ret = register_chrdev_region(data->devno, 1, DEV_NAME);
	} else {
		ret = alloc_chrdev_region(&data->devno, dev_minor, 1, DEV_NAME);
	}
	if (ret < 0) {
		AW9818_DEBUGP("register char dev failed\n");
		goto fail1;
	}

	cdev_init(&data->cdev, &aw9818_fops);

	ret = cdev_add(&data->cdev, data->devno, 1);
	if (ret) {
		AW9818_DEBUGP("error add a char dev");
		goto fail2;
	}

	data->class = class_create(THIS_MODULE, DEV_NAME);
	data->device = device_create(data->class, NULL, data->devno, NULL, DEV_NAME);

	return 0;

      fail2:
	cdev_del(&data->cdev);
      fail1:
	unregister_chrdev_region(data->devno, 1);
	return ret;
}

void aw9818_mic_key_handler(bool ismute)
{
	unsigned int cmd;
	struct led_ctrl_arg ctrl_arg;

	cmd = ismute ? AW9818_LEDS_EFFECT_KEYMUTE : AW9818_LEDS_EFFECT_KEYUNMUTE;

	memset(&ctrl_arg, 0, sizeof(ctrl_arg));
	ctrl_arg.is_mute = ! !ismute;

	do_ctrl_event(cmd, &ctrl_arg);
}

EXPORT_SYMBOL_GPL(aw9818_mic_key_handler);

/*
 *loop waitting "start compete sigal" from userspace !
 */
static void aw9818_startup(void)
{
	unsigned int cmd = AW9818_LEDS_EFFECT_STARTUP;

	do_ctrl_event(cmd, NULL);
}

/**************************************************************************************************************/

static int __devinit aw9818_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* like Singleton getInstance() */
	static struct aw9818_priv *data = NULL;

	/* check i2c-func */
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
		return -EIO;

	/* malloc priv-data */
	if (data == NULL) {
		data = devm_kzalloc(&client->dev, sizeof(struct aw9818_priv), GFP_KERNEL);
		if (!data)
			return -ENOMEM;
		g_aw9818 = data;
	}
	//i2c client can get priv somewhere i2c_set_clientdata(client, data);

	if (id->driver_data == AW9818_2) {	/* all chips probe, then call function */
		AW9818_DEBUGP("all chips probe, then call function\n");
		data->i2c_cli[1] = client;
		if (0 != aw9818_led_init()) {
			AW9818_DEBUGP("aw9818_led_init failed\n");
			goto fail1;
		}
		if (0 != aw9818_setup_cdev(data)) {
			AW9818_DEBUGP("aw9818_setup_cdev failed\n");
			goto fail2;
		}
#ifdef A810_BOARD_TEST
		aw9818_test();
#else
		aw9818_startup();
#endif
	} else if (id->driver_data == AW9818_1) {
		AW9818_DEBUGP("id=0 chips probe\n");
		data->i2c_cli[0] = client;
	}

	return 0;

//FIXME ... uninit()
      fail2:
      fail1:
	AW9818_DEBUGP("F************K, probe failed\n");
	return -1;
}

static int aw9818_i2c_remove(struct i2c_client *client)
{
//FIXME 
	/* 1.priv data auto-free by devm_kzalloc */

	/* 2.uninit */
	aw9818_led_uninit();

	/* 3.dev delete */

	return 0;
}

static struct i2c_board_info aw9818_i2c_board_info[] = {
	{I2C_BOARD_INFO("aw9818_1", 0x3a),},
	{I2C_BOARD_INFO("aw9818_2", 0x3b),},
};

static const struct i2c_device_id aw9818_i2c_id[] = {
	{"aw9818_1", AW9818_1},
	{"aw9818_2", AW9818_2},
	{},
};

MODULE_DEVICE_TABLE(i2c, aw9818_i2c_id);

static struct i2c_driver aw9818_i2c_driver = {
	.driver = {
		   .name = "hello aw9818",
		   },
	.probe = aw9818_i2c_probe,
	.id_table = aw9818_i2c_id,
	.remove = aw9818_i2c_remove,
};

static void aw9818_shdn_on()
{
	int gpio_num;

	gpio_num = ('L' - 'A') * 32 + 7;	//R16 Board IO:PL07
	gpio_request(gpio_num, "awinic shdn");
	gpio_direction_output(gpio_num, 1);
	msleep(5);
	gpio_set_value(gpio_num, 1);
	msleep(5);
}

static __init int module_aw9818_init(void)
{
	struct i2c_adapter *adapter = NULL;
	struct i2c_client *client = NULL;
	int i = 0;
	AW9818_DEBUGP("Hello World\n");

	aw9818_shdn_on();

	adapter = i2c_get_adapter(AW9818_CHANNEL);
	if (!adapter)
		return -ENODEV;

	for (; i < sizeof(aw9818_i2c_board_info) / sizeof(aw9818_i2c_board_info[0]); i++) {
		client = NULL;
		client = i2c_new_device(adapter, &aw9818_i2c_board_info[i]);
		if (!client)
			return -ENODEV;
	}
	i2c_put_adapter(adapter);

	return i2c_add_driver(&aw9818_i2c_driver);
}

static __exit void module_aw9818_exit(void)
{
	AW9818_DEBUGP("Byebye\n");
	i2c_del_driver(&aw9818_i2c_driver);
}

/* insmod xxx.ko */
module_init(module_aw9818_init);
/* rmmod xxx.ko */
module_exit(module_aw9818_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("oneface");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("aw9818 led for a810 proj");
